///////////////////////////////
//  Makumba, Makumba tag library
//  Copyright (C) 2000-2003  http://www.makumba.org
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
//  -------------
//  $Id$
//  $Name$
/////////////////////////////////////

package org.makumba;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.UnsupportedEncodingException;

import org.makumba.commons.LongData;
import org.makumba.commons.RuntimeWrappedException;

/**
 * A makumba text, a byte array that can be converted to String when needed. When texts get big, they are swapped on
 * disk. Text swapping is logged (see {@link java.util.logging.Logger},
 * {@link org.makumba.MakumbaSystem#setLoggingRoot(java.lang.String)} in the logger <b><code>"abstr.longContent"</code>
 * </b> with {@link java.util.logging.Level#FINE} logging level.
 */
public class Text {
    // first alternative
    InputStream source;

    int len;

    // second alternative
    LongData ld;

    public static int FILE_LIMIT;
    static {
        String s = null;
        try {
            s = System.getProperty("makumba.long-content");
        } catch (SecurityException se) {
        } // for applets
        if (s != null) {
            FILE_LIMIT = Integer.parseInt(s.trim());
        } else {
            FILE_LIMIT = 32768;
        }
    }

    /**
     * make a Text from the given input stream, with the given length The content in the input stream must match the
     * length. No diskswap will be attempted. If you wish for the info to be swapped to disk, use the constructor
     * Text(InputStream)
     */
    public Text(InputStream is, int length) {
        this.source = is;
        this.len = length;
    }

    /** make a Text by reading the input stream. If there is a lot of data, it will be swapped to disk */
    public Text(InputStream is) {
        try {
            ld = new LongData();
            ld.appendFrom(is);
            this.source = ld.getInputStream();
            this.len = ld.getLength();
        } catch (IOException e) {
            throw new RuntimeWrappedException(e);
        }
    }

    /** make a Text by reading the input stream. If there is a lot of data, it will be swapped to disk */
    public Text(Reader is) {
        try {
            ld = new LongData();
            ld.appendFrom(is);
            this.source = ld.getInputStream();
            this.len = ld.getLength();
        } catch (IOException e) {
            throw new RuntimeWrappedException(e);
        }
    }

    /** make a Text from the String. If there is a lot of data, it will be swapped to disk */
    public Text(String s) {

        try {
            byte[] b = s.getBytes("UTF-8");
            init(b, 0, b.length);
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }

    /** make a Text from the given byte array. If there is a lot of data, it will be swapped to disk */
    public Text(byte[] b) {
        this(b, 0, b.length);
    }

    /** make a Text from the given byte array. If there is a lot of data, it will be swapped to disk */
    public Text(byte[] b, int start, int length) {
        init(b, start, length);

    }

    void init(byte[] b, int start, int length) {
        len = length;
        try {
            ld = new LongData();
            ld.appendFrom(new ByteArrayInputStream(b, start, length));
            source = ld.getInputStream();
        } catch (IOException e) {
            throw new RuntimeWrappedException(e);
        }

    }

    /** Read the text content as a binary stream. Recommended for long content */
    public InputStream toBinaryStream() {
        if (source == null) {
            throw new MakumbaError("texts indicated by stream can only be consumed once");
        }
        if (ld != null) {
            try {
                return ld.getInputStream();
            } catch (IOException e) {
                throw new RuntimeWrappedException(e);
            }
        }
        InputStream s = source;
        source = null;
        return s;
    }

    public int length() {
        return len;
    }

    /** convenience method for making a text out of another Text, InputStream, String, or byte[] */
    public static Text getText(Object value) {
        if (value instanceof Text) {
            return (Text) value;
        }
        if (value instanceof InputStream) {
            return new Text((InputStream) value);
        }
        if (value instanceof Reader) {
            return new Text((Reader) value);
        }
        if (value instanceof String) {
            return new Text((String) value);
        }
        if (value instanceof byte[]) {
            return new Text((byte[]) value);
        }
        throw new InvalidValueException("unsupported type to make text of " + value.getClass());
    }

    /** converts the text content to a string */
    @Override
    public String toString() {
        ByteArrayOutputStream bo = new ByteArrayOutputStream(len < FILE_LIMIT ? len : FILE_LIMIT);
        try {
            writeTo(bo);
        } catch (IOException e) {
            throw new RuntimeWrappedException(e);
        }

        try {
            if (org.makumba.db.makumba.sql.Database.supportsUTF8()) {
                /*
                 * String asd = new String(bo.toByteArray(), "UTF-8"); System.out.println("UTF1: "+(new
                 * String(bo.toByteArray()))); System.out.println("UTF: "+asd);
                 */
                return new String(bo.toByteArray(), "UTF-8");
            }
        } catch (UnsupportedEncodingException e) {
        }

        return new String(bo.toByteArray());
    }

    public String toShortString(int length) {
        if (length > len) {
            length = len;
        }
        if (length > FILE_LIMIT) {
            length = FILE_LIMIT;
        }
        ByteArrayOutputStream bo = new ByteArrayOutputStream(length + 20);

        InputStream is = toBinaryStream();
        byte[] b = new byte[length];
        try {
            try {

                int n = is.read(b, 0, b.length);
                if (n != -1) {
                    bo.write(b, 0, n);
                }
                bo.write("... (length: ".getBytes());
                bo.write(new Integer(len).toString().getBytes());
                bo.write(")".getBytes());
                return bo.toString();
            } finally {
                bo.close();
                is.close();

            }
        } catch (IOException ioe) {
            throw new RuntimeWrappedException(ioe);
        }

    }

    /** write content to the given output stream */
    public void writeTo(OutputStream o) throws IOException {
        byte[] b = new byte[len < FILE_LIMIT ? len : FILE_LIMIT];
        InputStream is = toBinaryStream();

        try {
            int n;
            while ((n = is.read(b, 0, b.length)) != -1) {
                o.write(b, 0, n);
            }
        } finally {
            o.close();
            is.close();
        }
    }

    /** compare the content to the content of a given stream, and closes the stream */
    public boolean compare(InputStream is) throws IOException {
        InputStream is1 = toBinaryStream();
        try {
            int n, m;
            int i = 0;
            while ((n = is.read()) != -1) {
                if (n != (m = is1.read())) {
                    java.util.logging.Logger.getLogger("org.makumba.debug.abstr").severe(m + " " + n + " " + i);
                    return false;
                } else {
                    i++;
                }
            }
            return is1.read() == -1;
        } finally {
            is.close();
            is1.close();
        }
    }

    /** Indicates whether the other object is "equal to" this Text. */
    @Override
    public boolean equals(Object other) {
        if (other == null) {
            return false;
        }
        if (this.getClass() != other.getClass()) {
            return false;
        }
        if (this.length() != ((Text) other).length()) {
            return false;
        }
        try {
            return this.compare(((Text) other).toBinaryStream());
        } catch (IOException e) {
            return false;
        }
    }

    /** gets this Text as a String, which is potentially harmful for memory */
    public String getString() {
        return toString();
    }

}
